#include "ParallelLight.h"

using namespace OBR;

/* ----------------------------- static members ----------------------------- */

std::shared_ptr<CudaProgram> ParallelLight::program = nullptr;
CudaBuffer ParallelLight::sbt_data_payload;

/* --------------------------------- methods -------------------------------- */

ParallelLight::ParallelLight() : DeltaLight("shaders/Light/ParallelLight.cu")
{
    // init program opt & desc
    this->program_desc.callables.entryFunctionNameDC = "__direct_callable__parallel_light";
    this->program_desc.kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES;
    this->program_desc.flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE;
}

ParallelLight::~ParallelLight()
{
}

void ParallelLight::_buildFromConfig(const Json::Value &config)
{
    this->direction =
        make_float3(config["direction"][0].asFloat(), config["direction"][1].asFloat(),
                    config["direction"][2].asFloat());
    ConfigHelpers::loadSpectrum(config["radiance"], &(this->radiance));
}

void ParallelLight::uploadVirtualProgramPayload(
    std::vector<std::shared_ptr<VirtualProgramEntry>> instances)
{
    const size_t n_instances = instances.size();
    const size_t data_size = sizeof(ParallelLightData);
    const size_t memory_size = n_instances * data_size;

    // collect data
    ParallelLightData *tmpdata = new ParallelLightData[n_instances];
    for (size_t i = 0; i < n_instances; i++)
    {
        auto l = std::dynamic_pointer_cast<ParallelLight>(instances[i]);
        tmpdata[i].direction = l->direction;
        tmpdata[i].radiance = l->radiance;
    }

    // upload data and free tmp
    ParallelLight::sbt_data_payload.resize(memory_size);
    ParallelLight::sbt_data_payload.upload(tmpdata, memory_size);
    delete[] tmpdata;
}

CUdeviceptr ParallelLight::getPayloadDevicePtr()
{
    return ParallelLight::sbt_data_payload.d_pointer();
}

void ParallelLight::setProgram(std::shared_ptr<CudaProgram> prog)
{
    ParallelLight::program = prog;
}

std::shared_ptr<CudaProgram> ParallelLight::getProgram()
{
    return ParallelLight::program;
}

OptixProgramGroupOptions &ParallelLight::getProgramGroupOptions()
{
    return this->program_opt;
}

OptixProgramGroupDesc &ParallelLight::getProgramGroupDesc()
{
    this->program_desc.callables.moduleDC = this->cuda_module->optix_module;
    return this->program_desc;
}

void ParallelLight::updateSbtData()
{
    assert(this->sbt_data_payload.d_ptr != nullptr);
    ParallelLightData *d_loc =
        (ParallelLightData *)this->sbt_data_payload.d_ptr + this->program_instance_id;
    ParallelLightData data = {this->direction, this->radiance};
    CUDA_CHECK(cudaMemcpy(d_loc, &data, sizeof(ParallelLightData), cudaMemcpyHostToDevice));
}